`include "c3d_timescale.v"
`include "c3d_defines.v"

module c3d_rasterizer(
    topo, x, y, start, busy,
    blk_start, blk_ready,
    x_pos, y_pos,
    clk, rst
);

parameter WIDTH = `C3D_COORD_WIDTH;

parameter RESO_X = `C3D_RESOLUTION_X;
parameter RESO_Y = `C3D_RESOLUTION_Y;

parameter STEP_X = `C3D_RASTERIZE_STEP_X,
parameter STEP_Y = `C3D_RASTERIZE_STEP_Y;

input [`C3D_TOPOLOGY_WIDTH-1:0]     topo;
input signed [WIDTH-1:0]            x[2:0];
input signed [WIDTH-1:0]            y[2:0];
input                               start;
output                              busy;
output                              blk_start;
input                               blk_ready;
output signed [WIDTH-1:0]           x_pos;
output signed [WIDTH-1:0]           y_pos;
input                               clk;
input                               rst;

reg             blk_start;

localparam [1:0] STATE_IDLE = 2'b00,
                 STATE_BOUND = 2'b01,
                 STATE_RUN = 2'b10,
                 STATE_WAIT = 2'b11;
reg [1:0]       state;

assign busy = (state != STATE_IDLE);

reg signed [WIDTH*2-1:0]        line_a[2:0];
reg signed [WIDTH*2-1:0]        line_b[2:0];
reg signed [WIDTH*2-1:0]        line_c[2:0];
reg [2:0]                       tri_ok;
wire                            tri_valid;

reg signed [WIDTH-1:0]  xmin;
reg signed [WIDTH-1:0]  xmax;
reg signed [WIDTH-1:0]  ymin;
reg signed [WIDTH-1:0]  ymax;

reg signed [WIDTH-1:0]  x_start;
reg signed [WIDTH-1:0]  y_start;
reg signed [WIDTH-1:0]  x_end;
reg signed [WIDTH-1:0]  y_end;
reg signed [WIDTH-1:0]  x_pos;
reg signed [WIDTH-1:0]  y_pos;

always @(x, y) begin
    xmin = (x[0] < x[1] ? (x[0] < x[2] ? x[0] : x[2]) : (x[1] < x[2] ? x[1] : x[2]));
    xmax = (x[0] > x[1] ? (x[0] > x[2] ? x[0] : x[2]) : (x[1] > x[2] ? x[1] : x[2]));
    ymin = (y[0] < y[1] ? (y[0] < y[2] ? y[0] : y[2]) : (y[1] < y[2] ? y[1] : y[2]));
    ymax = (y[0] > y[1] ? (y[0] > y[2] ? y[0] : y[2]) : (y[1] > y[2] ? y[1] : y[2]));
    x_start = (xmin < 16'd0 ? 16'd0 : xmin);
    x_end = (xmax > RESO_X ? RESO_X : xmax);
    y_start = (ymin < 16'd0 ? 16'd0 : ymin);
    y_end = (ymax > RESO_Y ? RESO_Y : ymax);
end

always @(x, y, x_pos, y_pos) begin
    integer i, j;
    for (i = 0; i < 3; i = i + 1) begin : is_valid
        j = (i + 1) % 3;
        line_a[i] = y[i] - y[j];
        line_b[i] = x[j] - x[i];
        line_c[i] = x[i] * y[j] - x[j] * y[i];
        tri_ok[i] = (line_a[i] * x_pos + line_b[i] * y_pos + line_c[i] <= 32'sd0) ||
               (line_a[i] * (x_pos + STEP_X - 1) + line_b[i] * y_pos + line_c[i] <= 32'sd0) ||
               (line_a[i] * x_pos + line_b[i] * (y_pos + STEP_Y - 1) + line_c[i] <= 32'sd0) ||
               (line_a[i] * (x_pos + STEP_X - 1) + line_b[i] * (y_pos + STEP_Y - 1) + line_c[i] <= 32'sd0);
    end
    tri_valid = &tri_ok;
end

always @(posedge clk or negedge rst) begin
    if (!rst) begin
        blk_start <= 1'b0;
        state <= STATE_IDLE;
    end
    else begin
        case (state)
            STATE_IDLE: begin
                if (start) begin
                    state <= STATE_BOUND;
                end
            end
            STATE_BOUND: begin
                x_pos <= x_start;
                y_pos <= y_start;
                state <= STATE_RUN;
            end
            STATE_RUN: begin
                if (tri_valid) begin
                    blk_start <= 1'b1;
                    state <= STATE_WAIT;
                end
                else begin
                    if (x_pos + STEP_X >= x_end) begin
                        if (y_pos + STEP_Y >= y_end) begin
                            state <= STATE_IDLE;
                        end
                        else begin
                            x_pos <= x_start;
                            y_pos <= y_pos + STEP_Y;
                        end
                    end
                    else begin
                        x_pos <= x_pos + STEP_X;
                    end
                end
            end
            STATE_WAIT: begin
                blk_start <= 1'b0;
                if (blk_ready) begin
                    state <= STATE_RUN;
                    if (x_pos + STEP_X >= x_end) begin
                        if (y_pos + STEP_Y >= y_end) begin
                            state <= STATE_IDLE;
                        end
                        else begin
                            x_pos <= x_start;
                            y_pos <= y_pos + STEP_Y;
                        end
                    end
                    else begin
                        x_pos <= x_pos + STEP_X;
                    end
                end
            end
        endcase
    end
end

endmodule